﻿using System.Collections.Generic;
using Beaker.OpenCube.Dynamic;
using Beaker.OpenCube.Game;
using Beaker.OpenCube.Render.Cameras;
using Beaker.OpenCube.Render.Lighting;
using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Sampling;
using Beaker.OpenCube.Render.Shapes;
using Beaker.OpenCube.Render.Materials;
using Beaker.OpenCube.Render.Tracing;
using GalaSoft.MvvmLight.Command;
using System;
using System.Reactive.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Color = Beaker.OpenCube.Render.Maths.Color;

namespace Beaker.OpenCube.Render.Test.ViewModels
{
	public sealed class MainViewModel
		: ViewModel
	{
		private readonly DynamicGame _game;
		private readonly DynamicTexturePack _texturePack;

		public MainViewModel()
		{
			_startCommand = new RelayCommand( Start, CanStart );
			_samplers = new Dictionary<string, Func<int, ISampler>>
			{
				{ "Ordered", n => new OrderedSampler( n ) },
				{ "Noise", n => new RandomSampler( n ) },
				{ "Jitter", n => new JitterSampler( n ) }
			};
			_selectedSampler =
				new KeyValuePair<string, Func<int, ISampler>>(
					"Ordered",
					_samplers["Ordered"]
				);

			_game = new DynamicGame();
			_texturePack = _game.OpenDefaultTexturePack();
		}

		private ImageSource _imageSource;

		public ImageSource ImageSource
		{
			get { return _imageSource; }
			private set { SetValue( ref _imageSource, value ); }
		}

		private int _rayDepth = 5;

		/// <summary>
		/// Gets or sets the maximum ray depth
		/// </summary>
		public int RayDepth
		{
			get { return _rayDepth; }
			set { SetValue( ref _rayDepth, value ); }
		}

		private int _samplesPerPixel = 1;

		public int SamplesPerPixel
		{
			get { return _samplesPerPixel; }
			set { SetValue( ref _samplesPerPixel, value ); }
		}

		private readonly Dictionary<string, Func<int, ISampler>> _samplers;

		public Dictionary<string, Func<int, ISampler>> Samplers
		{
			get { return _samplers; }
		}

		private readonly RelayCommand _startCommand;

		public ICommand StartCommand
		{
			get { return _startCommand; }
		}

		private KeyValuePair<string, Func<int, ISampler>> _selectedSampler;

		public KeyValuePair<string, Func<int, ISampler>> SelectedSampler
		{
			get { return _selectedSampler; }
			set { SetValue( ref _selectedSampler, value ); }
		}

		private bool _isRayTracing;
		private WriteableBitmap _bitmap;

		private void Start()
		{
			if( CanStart() )
			{
				_isRayTracing = true;
				_startCommand.RaiseCanExecuteChanged();

				int width = 640;
				int height = 360;

				if( _bitmap == null )
					_bitmap = new WriteableBitmap( width, height, 96, 96, PixelFormats.Bgr24, null );
				ImageSource = _bitmap;

				// create a raytracer
				ITracer tracer = new RayTracer( RayDepth );
				// create a scene to raytrace
				Scene scene = CreateTestScene();
				// create the viewplan to render it onto
				ISampler sampler = SelectedSampler.Value( SamplesPerPixel );
				ViewPlane plane = new ViewPlane( width, height, sampler, pixelSize: .5 );

				// start rendering the scene onto the plane
				var observable = tracer.Render( scene, plane );
				observable
					.ObserveOnDispatcher()
					.Subscribe(
						update =>
						{
							LineRenderUpdate line = update as LineRenderUpdate;
							if( line != null )
							{
								var pixels = line.GetPixels();
								byte[] buffer = new byte[width * 3];
								int index = 0;
								foreach( Color pixel in pixels )
								{
									var c = pixel.ToDisplayColor();
									buffer[index++] = c.B;
									buffer[index++] = c.G;
									buffer[index++] = c.R;
								}
								_bitmap.WritePixels( new Int32Rect( 0, line.LineNumber, width, 1 ), buffer, width * 3, 0 );
							}
						},
						error =>
						{
							throw new TargetInvocationException( error );
						},
						() =>
						{
							_isRayTracing = false;
							_startCommand.RaiseCanExecuteChanged();
						}
					);
			}
		}

		private bool CanStart()
		{
			return _isRayTracing == false && SelectedSampler.Value != null;
		}

		private Scene CreateTestScene()
		{
			return
				new Scene(
				//new ThinLensCamera(
				//	position: new Point3( 3d, 2d, 4d ),
				//	lookAt: new Point3( -1d, 0.5d, 0d ),
				//	up: new Vector3( 0, 1, 0 ),
				//	lensSampler: new JitterSampler( 500 ),
				//	lensRadius: 1d,
				//	focalPlaneDistance: 50d,
				//	viewPlaneDistance: 500d,
				//	zoomFactor: 1d
				//),
					new PinholeCamera(
						cameraPosition: new Point3( 3d, 2d, 4d ),
						lookAt: new Point3( -1d, 0.5d, 0d ),
						up: new Vector3( 0, 1, 0 ),
						viewPlaneDistance: 500d,
						zoomFactor: 0.5d
						),
					new IShape[]
					{
						// plane on y=0 (floor), with normal vector straight up
						new Plane( Point3.Zero, new Vector3( 0d, 1d, 0d ), new MatteMaterial( new Color(1,0.5,0.1),.5,.5) ),
						new GameBlock(new Point3(-0.5d, 0.5d, 0.5d), _texturePack.GetBlockTexture( Block.WoodOak ) ),
						new GameBlock(new Point3(-1.5d, 0.5d, 0.5d), _texturePack.GetBlockTexture( Block.LapisLazuliOre ) ),
						new GameBlock(new Point3(-1.5d, 1.5d, 0.5d), _texturePack.GetBlockTexture( Block.Cobblestone ) ),
						new GameBlock(new Point3(-2.5d, 0.5d, 0.5d), _texturePack.GetBlockTexture( Block.Grass ) ),
						// new Box( new Point3( -1.5d, 0d, 0.5d ), new Point3( -0.5d, 5d, 1.5d ), new Shiny() ),
						//new Triangle( new Point3(0,0.5,0), new Point3(5,0.5,-2), new Point3(-2,0.5,-2), new Shiny()  )
					},
					new ILight[]
					{
//						new PointLight( new Point3( -2d, 2.5d, 0d ), new Color( 0.5, 0, 0 ) ),
//						new PointLight( new Point3( 1.5d, 2.5d, 1.5d ), new Color( 0, 0.5, 0 ) ),
//						new PointLight( new Point3( 1.5d, 2.5d, -1.5d ), new Color( 0, 0, 0.5 ) ),
						new AmbientLight( Color.White, 0.1d ), 
						new DirectionalLight( new Point3(-1.5d,1d,3d), Color.White, new Vector3(-0.25,-1,0), 0.2d ), 
					}
				);
		}
	}
}
